// import './yuanxing'
// import EventEmitter from './eventEmitter'
// import './callAndApply'
// import './bind'
// import './new'
// import './../example/react/app.jsx'
// import './debounce'
// import './throttle'
// import './sort'
// import './grid'
// import './promise'
// import './spider'
// import './inherit'

// let emitter = new EventEmitter()
// function testFun1() {
//     console.log('test1')
// }
// function testFun2() {
//     console.log('test2')
// }
// function myEventFun(arg) {
//     console.log('myEvent', arg)
// }
// emitter.on('test', testFun1)    //支持链式调用
// .once('test', testFun2)         //同一事件可以订阅多个handler, 支持once订阅只会触发一次
// .on('myEvent', myEventFun)      //订阅其他事件
// .emit('test')                   //派发test事件
// .emit('test')                   //只会触发test1
// .emit('myEvent', 1)             //派发myEvent事件并传值
// .on('test', testFun2)           //继续监听test，执行testFun2
// .emit('test')                   //触发testFun1与testFun2
// .off('test', testFun1)          //test事件取消执行testFun1
// .emit('test')
// .off('test')                    //off不传handler取消test的所有handler
// .emit('test')
// .allOff()                       //取消所有事件handler，传入事件名只会取消特定事件的所有handler


// 实现数组的splice
// const list = [1, 2, 3, 4, 5, 6, 7]
// console.log(list)
// // 从start开始删除count个元素，并插入...addList, 返回被删除的元素组成的数组
// Array.prototype.splice2 = function(start, count, ...addList) {
//     // 处理start
//     if (start < 0) {
//         if (Math.abs(start) > this.length) {
//             start = 0
//         } else {
//             start = this.length + start
//         }
//     }
//     // 处理count
//     if (typeof count !== 'number') {
//         count = this.length - start
//     } else {
//         if (count < 0) {
//             count = 0
//         }
//     }
//     let delList = this.slice(start, start + count)
//     let right = this.slice(start + count, this.length)
//     this.length = start
//     this.push(...(addList.concat(right)))
//     return delList
// }
// let del = list.splice2(-4, 3, 'test1', 'test2')
// console.log(del)
// console.log(list)


// 实现数组slice
// String.prototype.slice2 = function(start, end) {
//     let str = ''
//     for(let i = start; i < end; i++) {
//         str += this[i]
//     }
//     return str
// }


// 实现字符串indexOf
// String.prototype.indexOf2 = function(str, start = 0) {
//     //从start位置开始检测this字符串中有没有含有str，并返回下标，没有返回-1
//     for(let i = start; i < this.length - str.length + 1; i++) {
//         console.log(this.slice2(i, i + str.length))
//         if (str === this.slice2(i, i + str.length)) {
//             return i
//         }
//     }
//     return -1
// }
// let str = 'abcdcba'
// console.log(str.indexOf2('ba', 2))



//二分查找，二分查找的数组都是从小到大顺序排列，寻找某一个数字存在的位置。
// Array.prototype.indexOf2 = function(target) {
//     let left = 0
//     let right = this.length - 1
//     while(left <= right){
//         let mid = Math.floor((left + right) / 2)
//         if (this[mid] === target) {  //中间数字等于要查找的数字，但是不一定是第一次或最后一次出现的位置(左边界和右边界)
//             if (left === mid) {
//                 return mid
//             } else {
//                 right = mid //寻找左边界，收缩右边界
//                 // left = mid //寻找右边界，收缩左边界
//             }
//         } else if (this[mid] > target) {
//             right = mid - 1
//         } else {
//             left = mid + 1
//         }
//     }
//     return -1
// }
// let list = [0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 5, 5, 6, 7, 8, 9]
// console.log(list.indexOf2(5))   // 11


// 生成器函数与生成器
// function *gen1() {
//     yield 'a'
//     yield 'b'
//     yield 'c'
//     yield 'd'
//     return 'gen1完成'
// }
// function *gen2() {
//     const val = yield* gen1()   //如果gen1是生成器函数，则需要用yield*来调用，会自动把gen1的生成器执行完毕。
//     console.log(val)
//     return 'gen2完成'
// }

// let g1 = gen1()  //gen1生成器函数的生成器
// console.log(g1.next())   //调用一次next完成一步
// console.log(g1.next())
// console.log(g1.next())
// console.log(g1.next())
// console.log(g1.next())  // {value: undefined, done: true} 生成器函数执行完毕

// let g2 = gen2()
// console.log(g2.next())
// console.log(g2.next())
// console.log(g2.next())
// console.log(g2.next())
// console.log(g2.next())

// function *getMult(x) {
//     if (x < 3) {
//         x = yield* getMult(x + 1)
//     }
//     console.log(x)
//     return x * 2
// }
// const it = getMult(1)
// console.log(it.next().value)

// function *sum(a) {
//     const b = yield
//     const c = yield
//     return a + b + c
// }
// let sumGen = sum(1)
// console.log(sumGen.next())  //第一次调用next是为了开始执行生成器，无需传参
// console.log(sumGen.next(2)) //第一个yield
// console.log(sumGen.next(3))



// Proxy类，实例化的时候构造函数接收第一个参数为被代理的对象或数组，第二个参数为对象，对象属性为对象属性的get和set等函数，与Object.defineProperty的区别是他可以代理数组
    // 返回Proxy实体对象(被代理)
// let obj = {
//     name: 'xiaohei'
// }
// let proxy = new Proxy(obj, {
//     get(obj, prop) {    //当前被代理的对象，当前被读取的属性。
//         console.log('-------', prop)
//         if(prop in obj) {
//             return obj[prop]
//         } else {
//             return 0
//         }
//     },
//     set(obj, prop, value, recriver) {   //目标对象，属性，属性值，
//         if (prop === 'age') {
//             if (typeof value !== 'number') {    //age字段只允许设置为number
//                 throw('age must be a number')
//             } else {
//                 obj[prop] = value
//                 return true
//             }
//         }
//     },
//     // ownKeys(obj) {
//     //     return Object.keys(obj).filter(v => v !== 'age')    //如果某一属性不想被for in遍历出来的话，可以使用ownKeys返回一个处理过的数组。
//     //     // return ['a', 'b', 'c']  //如果返回对象中不存在属性的话，for in 不会列出它们。（因为Object.keys只返回带有enumerable标志的可枚举属性，如果不可被枚举，则跳过）
//     // },
//     getOwnPropertyDescriptor(obj, prop) {
//         return {
//             enumerable: false,  //配置可枚举
//             configurable: true, //设置为false表示不可被改变
//         }
//     },
//     deleteProperty(obj, prop) {
//         if (prop === 'age') {   //不允许age字段被删除
//             throw('age is not access delete')
//         } else {
//             delete obj[prop]
//             return true
//         }
//     },
//     has(obj, prop) {
//         console.log(prop)
//         if (prop === 'testHas') {   // 代理in操作符 'testHas' in obj === true
//             return true
//         } else {
//             return prop in obj
//         }
//     }
// })
// console.log(proxy)
// //Proxy的实体类
//     // [[handler]]属性是用户自定义的get和set
//     // [[Target]]属性是被代理的对象(数组)
//     // [[IsRevoked]]
// proxy.age = 18
// // proxy.age = '20'    //跑出异常
// console.log(obj)
// for(let key in proxy) {
//     console.log(key)    //name age
// }
// console.log(proxy.test) //0
// console.log(obj.test)   //undefined，
// // console.log('testHas' in proxy) //true



// 判断字符串是否是一个回文数
// function isPalindrome(str) {
//     for(let i = 0; i < Math.floor(str.length / 2); i++) {
//         if (str[i] !== str[str.length - 1 - i]) {
//             return false
//         }
//     }
//     return true
// }
// 拓展：判断数字是不是回文数(不将数字转换为字符串)
// var isPalindrome = function(x) {
//     if(x < 0){
//         return false
//     }
//     if(x < 10){
//         return true
//     }else{
//         let n = Math.pow(10, Math.floor(Math.log10(x)))
//         while(x > 0){
//             if(Math.floor(x / n) !== x % 10){
//                 return false
//             }else{
//                 x = Math.floor((x % n) / 10)
//                 n = n / 100
//             }
//         }
//         return true
//     }
// }



//点击ul下的li输出下标
// document.getElementById('root').innerHTML = `
//     <ul id="test">
//         <li>1</li>
//         <li>2</li>
//         <li>3</li>
//         <li>4</li>
//     </ul>
// `
// let lis = document.querySelectorAll('#test li')
// Array.prototype.forEach.call(lis, (li, i) => {
//     li.onclick = function() {
//         console.log(i)
//     }
// })


// async function async1() {
//     console.log('async1 start')
//     await async2()
//     console.log('async1 end')
// }
// async function async2() {
//     console.log('async2')
// }
// console.log('script start')
// setTimeout(function() {
//     console.log('setTimeout')
// }, 0)
// async1()
// new Promise(function(resolve) {
//     console.log('promise1')
//     resolve()
// }).then(function() {
//     console.log('promise2')
// })
// console.log('script end')
// 'script start'       代码从上往下执行，产生一个执行栈，script start毫无疑问最先打印，遇到setTimeout，把延时执行的代码放入宏任务，继续执行主线任务
// 'async1 start'       执行async1函数，打印'async1 start' 并且await async2()
// 'async2'             直接执行async2, 并把async1里面await后面的代码放入微任务，继续主线执行(async await其实就是Promise的语法糖)
// 'promise1'           直接执行new Promise传入的函数，打印promise1，执行resolve，把then的函数放入微任务
// 'script end'         打印script end，主线代码执行完毕，开始询问微任务队列
// 'async1 end'         async1 end 最先进入微任务，最先打印
// 'promise2'           然后Promise.then执行，打印promise2，微任务队列已清空
// 'setTimeout'         执行宏任务队列，延时器已完毕，打印setTimeout


// server酱微信推送测试
// let sendkey = '22159-4af5e6f00fc5a0c611a506376d901437'
// window.fetch('https://pushbear.ftqq.com/sub', {
//     method: 'POST',
//     body: JSON.stringify({
//         sendkey,
//         text: '测试标题',
//         desp: '测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容',
//     })
// })

// 调起发送邮件
// let a = document.createElement('a')
// a.href='mailto:zfangbiao@gmail.com?subject=自定义主题&cc=抄送到次要收件人&bcc=密送收件人&body=' + `
// <!DOCTYPE html>
// <html lang="en">
//     <head>
//         <meta charset="UTF-8">
//         <meta http-equiv="X-UA-Compatible" content="IE=edge">
//         <meta name="viewport" content="width=device-width, initial-scale=1.0">
//         <title>Document</title>
//         <style>
//             *{margin: 0;padding: 0;box-sizing: border-box;}
//             .wrap{
//                 width: 750px;
//                 padding: 50px;
//                 /* padding-bottom: (50 - 24)px; */
//                 padding-bottom: 26px;
//                 display: flex;
//                 flex-wrap: wrap;
//                 border: 1px solid #777;
//             }
//             .wrap div {
//                 width: 200px;
//                 height: 220px;
//                 margin-left: 24px;
//                 margin-bottom: 24px;
//                 border: 1px solid steelblue;
//             }
//             .wrap div:nth-of-type(3n + 1) {
//                 margin-left: 0;
//             }
//         </style>
//     </head>
//     <body>
//         <div class="wrap">
//             <div></div>
//             <div></div>
//             <div></div>
//             <div></div>
//         </div>
//     </body>
// </html>
// `
// a.innerText='点击发送'
// document.getElementById('root').appendChild(a)



// // 判断数据类型
// function myTypeOf(a) {
//     let str = Object.prototype.toString.call(a).split(' ')[1]
//     return str.substr(0, str.length - 1).toLowerCase()
// }
// console.log(myTypeOf())


// // 数组扁平化
// let arr = [1, [2, 3, [4, [5, [6]]]]]
// Array.prototype.myFlat = function(a) {
//     // a 为数组item flat的层数
//     if (typeof a === 'undefined') a = 1 // 不传默认为1层
//     let arr = []
//     if (a > 0) {
//         this.forEach(v => {
//             if (Array.isArray(v)) {
//                 arr = arr.concat(v.myFlat(a - 1))   // 当前为数组，递归调用
//             } else {
//                 arr.push(v)
//             }
//         })
//         return arr
//     } else {
//         return this // 不需要再flat，直接返回调用者this
//     }
// }
// console.log(arr.flat(3))
// console.log(arr.myFlat(3))



// // 图片懒加载
// const srcList = [
//     'https://static.v-buy.com/upload/material_image/2020-08-24/a804cbb4f7332246e2b7d5f5153ca5ed9996b600.jpg',
//     'https://static.v-buy.com/upload/material_image/2019-04-05/14ea4a94524465147da418e75d81b7210e5319f8.png',
//     'https://static.v-buy.com/upload/material_image/2019-04-05/d09fed05ce148154cf24be0bcb4d03cf5c01f334.png',
//     'https://static.v-buy.com/upload/material_image/2019-04-05/b5bd194b5006de4b55678da3ba0e6479faaf733b.png',
//     'https://static.v-buy.com/upload/material_image/2019-04-05/48c8932b2832429868562018e38805d288754844.png',
//     'https://static.v-buy.com/upload/material_image/2019-04-05/5adaecdef92be24327a363b9bda6c64fe8df8c0e.png',
//     'https://static.v-buy.com/upload/material_image/2019-04-05/e731dc101128a6be0e55962eef89f65f70bde1f6.png',
//     'https://static.v-buy.com/upload/material_image/2019-04-05/de3e49eaed2c6853b55a705fd1bec329aefc95e1.png',
//     'https://static.v-buy.com/upload/material_image/2019-04-05/86f4451a41c35dbeac7045dd0d7a00578f0a226c.png',
//     'https://static.v-buy.com/upload/material_image/2019-04-05/ca3213f1cba97888375df052774e5b31be704cb1.png',
//     'https://static.v-buy.com/upload/material_image/2019-04-05/12593e75aeb558bf8bcd30052a9c7b6e912240a7.png',
//     'https://static.v-buy.com/upload/material_image/2019-04-05/d4e47d0726d512521de985f19347722e53a9bc2c.png',
//     'https://static.v-buy.com/upload/material_image/2019-04-05/dd371704efade46c53a9ca3236a1e1acbd105f67.png',
//     'https://static.v-buy.com/upload/material_image/2019-04-05/96128ace1ddf7d3465565d3d865c72b02383bca3.png',
//     'https://static.v-buy.com/upload/material_image/2019-04-05/2c58a1008c2df454220ea43ba8d1b1c75a35697f.png',
//     'https://static.v-buy.com/upload/material_image/2020-07-23/6551f895dbcfcb83a5da8cd0af49d855d9ef9b1d.jpg',
//     'https://static.v-buy.com/upload/material_image/2020-07-23/053e5c38f2e0be41b8e25bd65d1b2bfaf1e8274a.jpg',
//     'https://static.v-buy.com/upload/material_image/2020-07-23/053e5c38f2e0be41b8e25bd65d1b2bfaf1e8274a.jpg',
//     'https://static.v-buy.com/upload/material_image/2020-07-23/83ae9e2f39ffc55e4dd685a72301a6c499ea17cd.jpg',
//     'https://static.v-buy.com/upload/material_image/2020-07-23/e63612e11c42568d51fb09ffa0c4a37b96cdf902.jpg',
// ]
// let div = document.createElement('div')
// srcList.forEach(src => {
//     let wrap = document.createElement('div')
//     wrap.style.height = 200 + 'px'
//     let img = document.createElement('img')
//     img.dataset.src = src
//     wrap.appendChild(img)
//     div.appendChild(wrap)
// })
// document.getElementById('root').appendChild(div)
// let imgList = Array.from(document.getElementsByTagName('img'))
// let scrollAction = function() {
//     console.log('scroll')
//     let delList = []
//     imgList.forEach((v, i) => {
//         if (v.getBoundingClientRect().top < window.innerHeight) {
//             v.src = v.dataset.src
//             delete v.dataset.src
//             delList.push(i)
//         }
//     })
//     imgList = imgList.filter((_v, i) => !delList.includes(i))   //  过滤掉已经加载的图片。
//     if (!imgList.length) {  // 已经加载完毕
//         console.log('All images loaded, remove scroll-action')
//         document.removeEventListener('scroll', scrollAction)
//     }
// }
// document.addEventListener('scroll', scrollAction)
// scrollAction()



// // jsonp跨域
// // 因为浏览器同源策略，浏览器禁止向跨域站点发起http请求。
// // 跨域：协议，域名，端口号，必须全部相同，有一个不同就叫跨域。就会收到浏览器的同源策略限制。
// // jsonp通过创建一个script标签来加载非同源的文件(文件不受同源限制)，加载的script执行一个用户自定义的函数并传入结果。
// // jsonp跨域缺点：因为jsonp原理基于加载script标签，所以只能是get请求。
// function jsonp({ url, data, callback }) {
//     function getUrl() { // 拼接参数
//         let query = ''
//         data = data || {}
//         for (let key in data) {
//             if (data.hasOwnProperty(key)) {
//                 query && (query += '&')
//                 query += `${key}=${data[key]}`
//             }
//         }
//         return `${url}?${query}`
//     }
//     const script = document.createElement('script')
//     script.src = getUrl()
//     console.log(script.src)
//     document.body.appendChild(script)
//     window[data.cb] = callback
// }
// jsonp({
//     url: 'https://www.baidu.com/sugrec',
//     data: {
//         cb: 'jsonpCallback',
//         prod: 'pc',
//         wd: 'php天下第一',
//     },
//     callback: jsonpCallback,
// })
// function jsonpCallback(data) {
//     console.log(data)
// }


// // 作用域
// // 作用域分为词法作用域和动态作用域，js使用词法作用域，就是静态作用域
// let value = 1
// function foo() {
//     console.log(value)
// }
// function bar() {
//     let value = 2
//     foo()
// }
// bar()
// // 假如js采用词法作用域：执行bar函数，定于变量value，执行foo函数，从foo函数内部查找局部变量value，如果没有就根据书写foo的位置查找上一层，就是value = 1
// // 假如js采用动态作用域：执行bar函数，定义变量value，执行foo函数，从foo函数内部查找局部变量value，如果没有就从调用函数的作用域内查找，也就是value = 2
// // 因为js是词法作用域，所以打印结果为1。
// let scope = 'global'
// function checkscope1() {
//     let scope = 'local'
//     function f() {
//         return scope
//     }
//     return f()
// }
// function checkscope2() {
//     let scope = 'local'
//     function f() {
//         return scope
//     }
//     return f
// }
// console.log(checkscope1())
// console.log(checkscope2()())
// // 两段代码都会打印local，因为词法作用域中函数的作用域基于函数创建的位置。
// // 引用js犀牛书：JavaScript 函数的执行用到了作用域链，这个作用域链是在函数定义的时候创建的。嵌套的函数 f() 定义在这个作用域链里，其中的变量 scope 一定是局部变量，不管何时何地执行函数 f()，这种绑定在执行 f() 时依然有效。


// PWA通知
// let btn = document.createElement('button')
// btn.innerText = '点我'
// document.getElementById('root').appendChild(btn)
// btn.addEventListener('click', function() {
//     Notification.requestPermission().then(function(result) {    // 授权
//         if(result === 'granted') {
//             randomNotification()
//         }
//     })
// })
// function randomNotification() {
//     var options = {
//         body: '自定义内容',
//         icon: 'http://192.168.3.249:3000/images/aboutperson.jpg',
//     }
//     var notif = new Notification('自定义标题', options)
//     setTimeout(randomNotification, 3000)
// }
